/**
 * 该模块提供一个公共方法upload(option)
 * 这个方法会创建一个XMLHttpRequest并发送请求，用于上传文件，
 * 它会返回这个XMLHttpRequest对象
 * 使用示例：
 * const xhr = upload({
 *     action: 'http://localhost:3280/upload',
 *     method: 'POST',
 *     filename: 'fileDemo',
 *     file,
 *     data: undefined,
 *     withCredentials: false,
 *     headers: undefined,
 *     onProgress: e => {
 *         console.log('onProgress:', e.percent, e.speed);
 *     },
 *     onSuccess: (response, xhr) => {
 *         console.log('onSuccess:', response, xhr);
 *     },
 *     onError: e => {
 *         console.log('onError:', e);
 *     }
 * });
 * 取消掉当前上传请求：
 * xhr.abort();
 */

/**
 * 获取xhr请求出错的错误信息
 * @param {Object} option 请求参数配置
 * @param {XMLHttpRequest} xhr XMLHttpRequest对象
 * @returns {Error} err Error错误对象
 */
function getError(option, xhr) {
    let msg = `fail to ${option.method} ${option.action} ${xhr.status}`;

    if (xhr.response) {
        msg = `${xhr.response.error || xhr.response}`;
    } else if (xhr.responseText) {
        msg = `${xhr.responseText}`;
    }

    const err = new Error(msg);
    err.status = xhr.status;
    err.method = option.method;
    err.url = option.action;

    return err;
}
/**
 * 获取请求响应内容
 * @param {XMLHttpRequest} xhr XMLHttpRequest对象
 * @returns {String,JSON} text 文本信息或者JSON对象
 */
function getBody(xhr) {
    const text = xhr.responseText || xhr.response;
    if (!text) {
        return text;
    }

    try {
        return JSON.parse(text);
    } catch (e) {
        return text;
    }
}

/**
 * 获取当前时间戳
 * 用于计算上传速度
 */
function getTime() {
    const d = new Date();
    return d.getTime();
}

/**
 * 发起Ajax上传请求
 * @param {Object} option 请求参数配置
 * option.onProgress: (event: { percent: number }): void, // 上传过程中的回调
 * option.onError: (event: Error, body?: Object): void, // 上传出错时调用
 * option.onSuccess: (body: Object, xhr: XMLHttpRequest): void, // 上传成功后调用
 * option.data: Object, // 请求是要附带参数
 * option.filename: String, // 上传接口规定的文件字段名
 * option.file: File, // 要上传的文件对象
 * option.withCredentials: Boolean, // 是否携带cookie验证
 * option.action: String, // 上传接口，等于form标签的action属性
 * option.headers: Object, // 自定义请求头
 * 
 * @returns {XMLHttpRequest} xhr XMLHttpRequest对象
 */
export default function upload(option) {
    if (typeof XMLHttpRequest === 'undefined') {
        return;
    }

    const xhr = new XMLHttpRequest();

    // 处理进度跟踪事件
    if (option.onProgress && xhr.upload) {
        let startTime = 0; // 起始时间戳
        let previousTime = 0; // 记录初始或者前一次的时间戳
        let previousLoaded = 0; // 记录初始或者前一次已经上传的e.loaded

        xhr.upload.onloadstart = function loadStart(e) {
            startTime = getTime();
            previousTime = startTime; // 初始时间戳
            previousLoaded = 0; // 初始上传量
        };
        xhr.upload.onprogress = function progress(e) {
            if (e.total > 0) {
                e.percent = e.loaded / e.total * 100; // 上传进度

                // 计算上传速度
                const nowTime = getTime();
                const spendTime = (nowTime - previousTime) / 1000; // 单位秒
                e.speed = (e.loaded - previousLoaded) / spendTime; // 单位 B/s

                previousTime = nowTime; // 记录本次时间戳，下次计算速度用
                previousLoaded = e.loaded; // 记录截止当前的上传量，下次计算速度用
            }
            option.onProgress(e);
        };
    }

    // 处理请求携带数据
    const formData = new FormData();
    if (option.data) {
        Object.keys(option.data).forEach(key => {
            const value = option.data[key];
            // 处理数组格式的数据
            if (Array.isArray(value)) {
                value.forEach(item => {
                    formData.append(`${key}[]`, item);
                });
            } else {
                formData.append(key, option.data[key]);
            }
        });
    }
    // 携带要上传的文件对象
    if (option.file instanceof Blob) {
        formData.append(option.filename, option.file, option.file.name);
    } else {
        formData.append(option.filename, option.file);
    }

    // 处理错误监听事件
    xhr.onerror = function error(e) {
        option.onError(e);
    };

    // 处理请求完成状态
    xhr.onload = function onload() {
        if (xhr.status < 200 || xhr.status >= 300) {
            return option.onError(getError(option, xhr), getBody(xhr));
        }

        option.onSuccess(getBody(xhr), xhr);
    };

    xhr.open(option.method, option.action, true);

    // 必须在`.open()`之后处理，详情查看：https://github.com/enyo/dropzone/issues/179
    if (option.withCredentials && 'withCredentials' in xhr) {
        xhr.withCredentials = true;
    }

    // 处理自定义请求头信息
    const headers = option.headers || {};
    // 当设置headers['X-Requested-With'] = null时可以关闭默认的XMLHttpRequest header
    if (headers['X-Requested-With'] !== null) {
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    }
    for (let item in headers) {
        if (headers.hasOwnProperty(item) && headers[item] !== null) {
            xhr.setRequestHeader(item, headers[item]);
        }
    }

    // 发送请求
    xhr.send(formData);

    return xhr;
}